package com.itheima.wms.factory;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.pinda.utils.SpringUtils;
import com.itheima.wms.common.constants.CodeConstant;
import com.itheima.wms.common.utils.RedisLock;
import com.itheima.wms.dao.*;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Component
@Slf4j
@Order(value = 100)
public class CodeFactory implements CommandLineRunner {

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedisLock redisLock;

    private final static String KEY = "BILL-CODE-INCR";

    /**
     * mapper 和 code起始字符 的对应map
     */
    private final static Map<String, Class<? extends BaseMapper>> MAPPER_LIST = new HashMap() {{
        put(CodeConstant.WAREHOUSE, WarehouseMapper.class);
        put(CodeConstant.AREA, AreaMapper.class);
        put(CodeConstant.LOCATION, LocationMapper.class);
        put(CodeConstant.GOODSTYPE, GoodsTypeMapper.class);
        put(CodeConstant.GOODS, GoodsMapper.class);
        put(CodeConstant.CHECK, CheckMapper.class);
        put(CodeConstant.CHECKTASK, CheckTaskMapper.class);
        put(CodeConstant.INCREASEDECREASE, IncreaseDecreaseMapper.class);
        put(CodeConstant.RECEIPT, ReceiptMapper.class);
        put(CodeConstant.RECEIVING, ReceivingMapper.class);
        put(CodeConstant.GROUNDING, GroundingMapper.class);
        put(CodeConstant.OUTBOUND, OutboundMapper.class);
        put(CodeConstant.PICKING, PickingMapper.class);
        put(CodeConstant.HANDOVER, HandoverMapper.class);
        put(CodeConstant.OWNER, OwnerMapper.class);
    }};

    /**
     * 每一个code 对应的长度
     */
    private final static Map<String, Integer> CODE_LENGTH = new HashMap() {{
        put(CodeConstant.WAREHOUSE, 6);
        put(CodeConstant.AREA, 6);
        put(CodeConstant.LOCATION, 6);
        put(CodeConstant.GOODSTYPE, 6);
        put(CodeConstant.GOODS, 6);
        put(CodeConstant.CHECK, 6);
        put(CodeConstant.CHECKTASK, 6);
        put(CodeConstant.INCREASEDECREASE, 6);
        put(CodeConstant.RECEIPT, 6);
        put(CodeConstant.RECEIVING, 6);
        put(CodeConstant.GROUNDING, 6);
        put(CodeConstant.OUTBOUND, 6);
        put(CodeConstant.PICKING, 6);
        put(CodeConstant.HANDOVER, 6);
        put(CodeConstant.OWNER, 6);
    }};

    /**
     * code起始 和 原子对象的集合
     */
    private final static Map<String, RedisAtomicLong> CODE_ATOMIC = new HashMap<>();

    @Override
    public void run(String... args) {
        // 检查redis中是否已存在 存在则实例化到内存  不存在则加载
        Map<String, Class<? extends BaseMapper>> notExists = checkAllCodeAtomicAndGetNotExists();
        if (!CollectionUtils.isEmpty(notExists)) {
            // 获取数据库中的所有code值
            List<String> allCodeArray = getAllCodeByMapperList(notExists);
            // 将这code的int类型存入redis 并缓存redis对象
            buildAtomic(allCodeArray);
        }
    }

    /**
     * 获取下一个编号
     *
     * @param name
     * @return
     */
    public String nextCode(String name) {
        RedisAtomicLong redisAtomicLong = CODE_ATOMIC.get(name);
        long codeNum = redisAtomicLong.incrementAndGet();
        String sf = String.format("%0" + CODE_LENGTH.get(name) + "d", codeNum);
        return name + sf;
    }


    /**
     * 检查redis中是否存在
     * 存在则初始化
     * 不存在则返回到map中
     *
     * @return
     */
    private Map<String, Class<? extends BaseMapper>> checkAllCodeAtomicAndGetNotExists() {
        Map<String, Class<? extends BaseMapper>> notExists = new HashMap<>();
        MAPPER_LIST.forEach((key, value) -> {
            Boolean exists = redisTemplate.getConnectionFactory().getConnection().exists((KEY + "-" + key).getBytes());
            if (exists) {
                RedisAtomicLong redisAtomicLong = new RedisAtomicLong(KEY + "-" + key, redisTemplate.getConnectionFactory());
                CODE_ATOMIC.put(key, redisAtomicLong);
            } else {
                notExists.put(key, value);
            }
        });
        return notExists;
    }

    /**
     * 构建Atomic对象
     *
     * @param allCodeArray
     */
    private void buildAtomic(List<String> allCodeArray) {
        for (String code : allCodeArray) {
            String name = split(code)[0];
            Integer num = Integer.parseInt(split(code)[1]);
            RedisAtomicLong redisAtomicLong = new RedisAtomicLong(KEY + "-" + name, redisTemplate.getConnectionFactory());
            redisAtomicLong.set(num);
            CODE_ATOMIC.put(name, redisAtomicLong);
        }
    }

    /**
     * 获取数据库中的所有code值
     *
     * @param notExists
     * @return
     */
    private List<String> getAllCodeByMapperList(Map<String, Class<? extends BaseMapper>> notExists) {
        List<String> allCodeArray = new ArrayList<>();
        notExists.forEach((key, value) -> {
            BaseMapper mapper = SpringUtils.getBean(value);
            String code = getCode(mapper);
            if (null == code) {
                allCodeArray.add(key + "0");
            } else {
                allCodeArray.add(code);
            }
        });
        return allCodeArray;
    }

    /**
     * 获取最后一个code
     *
     * @param mapper
     * @param <M>
     * @return
     */
    @SneakyThrows
    private <M extends BaseMapper> String getCode(M mapper) {
        QueryWrapper wrapper = new QueryWrapper();
        wrapper.orderByDesc("create_time");
        wrapper.last(" limit 1");
        Object entity = mapper.selectOne(wrapper);

        if (null == entity) {
            return null;
        }

        Class<M> mClass = (Class<M>) entity.getClass();
        Method codeMethod = mClass.getMethod("getCode");
        String code = (String) codeMethod.invoke(entity);

        return code;
    }

    /**
     * 切分code
     *
     * @param code
     * @return [0] char [1] int
     */
    private String[] split(String code) {
        String[] part = code.split("(?<=\\D)(?=\\d)");
        return part;
    }
}
